package cn.cocho.android.mc.file.downloader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;

import android.accounts.NetworkErrorException;

import cn.cocho.android.mc.character.StringUtils;
import cn.cocho.android.mc.file.FileUtils;
import cn.cocho.android.mc.log.LoggerUtils;

/**
 * 多任务下载器（支持断点续传）
 * 
 * @author HAIKANG SONG
 * @2013年7月17日 @下午5:42:32
 */
public class Downloader {

	private static ConcurrentMap<String, FutureTask<?>> tasks = new ConcurrentHashMap<String, FutureTask<?>>();// 该集合属于线程安全的
	private static ThreadPoolExecutor executor;
	/**
	 * 存储下载出错的章节，以便在下载管理页面加以提示。
	 */
	public static Map<String, String> downloadError = new HashMap<String, String>();

	static {
		ThreadFactory threadFactory = new ThreadFactory() {

			@Override
			public Thread newThread(Runnable r) {
				Thread thread = new Thread(r);
				thread.setDaemon(true);
				thread.setName(Downloader.class.getSimpleName());
				thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {

					@Override
					public void uncaughtException(Thread thread, Throwable ex) {
						LoggerUtils.error(Downloader.class.getName(),
								"Exception happend when run async task in " + Downloader.class.getName(), ex);
					}
				});

				return thread;
			}
		};

		executor = new ThreadPoolExecutor(DownloaderContext.THREAD_COUNT, DownloaderContext.THREAD_COUNT, 0L, TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue<Runnable>(), threadFactory);

		executor.prestartAllCoreThreads();// 有新任务进入时即可启动线程
	}

	/**
	 * 是否有可用线程（比如限制线程数设置为2，如果当前使用线程为1则返回true,如果为2则返回false）
	 * 
	 * @return
	 * @author HAIKANG SONG
	 */
	public static boolean hasUsableThread() {
		return executor.getPoolSize() - executor.getActiveCount() > 0;
	}

	/**
	 * 提交任务
	 * 
	 * @param bean
	 *            任务所需的参数对象
	 * @param listener
	 * @author HAIKANG SONG
	 */
	public static void submitTask(final DownloaderBean bean, final DownloaderListener listener) {
		if (StringUtils.isBlank(bean.getUrl())) {
			return;
		}

		FutureTask<?> task = new FutureTask<Object>(new Callable<Object>() {

			@Override
			public Object call() throws Exception {
				download(bean, listener);
				return null;
			}
		});

		FutureTask<?> oldTask = tasks.putIfAbsent(bean.getThreadKey(), task);// 如果指定key的任务不存在则创建,但是返回值为null，存在则返回任务对象
		if (oldTask == null) {
			executor.submit(task);
		}
	}

	/**
	 * 取消指定的任务
	 * 
	 * @param threadKey
	 *            任务的主键
	 * @author HAIKANG SONG
	 */
	public static void cancelTask(String threadKey) {
		Future<?> future = tasks.get(threadKey);
		if (future == null) {
			return;
		}

		future.cancel(true);
		tasks.remove(threadKey);
		executor.purge();
	}

	private static void download(final DownloaderBean bean, final DownloaderListener listener) {
		if (listener != null) {
			listener.start(bean.getThreadKey());
		}

		
		File file = FileUtils.getFile(bean.getFileSavePath());
		if(!file.exists() && bean.getStartPosition()>0){//如果需要断点续传但是，上次下载的文件被删除了（如通过文件管理器删除），则从头开始下载
			bean.setStartPosition(0);
		}
		
		long endPosition = bean.getEndPosition();
		long startPosition = bean.getStartPosition();
		long downloadFileLength = 0;
		HttpGet httpGet = null;
		try {
			httpGet = new HttpGet(bean.getUrl());
			if (endPosition > 0 && startPosition > 0 && endPosition > startPosition) {// 使用分块下载（下载文件中指定的段）
				httpGet.addHeader("Range", "bytes=" + startPosition + "-" + endPosition);
			}

			HttpResponse response = doHttpRequest(httpGet, listener);
			if (Thread.currentThread().isInterrupted()) {//如果当前线程已经取消则应该终止流程
				return;
			}
			
			if (response == null) {
				listener.error(bean.getThreadKey(), new NetworkErrorException("download file has error! url[" + bean.getUrl()
						+ "], can't get HttpResponse by server"));
				return;
			}

			int statusCode = response.getStatusLine().getStatusCode();
			if ((statusCode != 206) && (statusCode != 200)) {
				if (listener != null) {
					listener.error(bean.getThreadKey(), new NetworkErrorException("download file has error! url[" + bean.getUrl()
							+ "],statusCode[" + statusCode + "]"));
				}
				return;
			}

			long startLocation = 0L;
			if (statusCode == 206) {// Partial Content
									// 客户发送了一个带有Range头的GET请求（分块请求），服务器完成了它
				startLocation = bean.getStartPosition();
			} else {// 如果是从头下载文件
				startLocation = 0l;
				bean.setStartPosition(startLocation);
			}

			if (endPosition <= 0) {// 如果文件结束位置没有值，则自动补充为文件的实际总长度
				Header[] headers = response.getHeaders("Content-Length");
				String contentLengthValue = headers[0].getValue();
				endPosition = Long.parseLong(contentLengthValue);
				bean.setEndPosition(endPosition);
				if (listener != null) {
					listener.progress(bean.getThreadKey(), startLocation, endPosition);
				}
			}

			InputStream inputStream = response.getEntity().getContent();
			// 创建随机读写类
			downloadFileLength = downloadContent(inputStream, bean, listener);
		} catch (Exception e) {
			if (listener != null) {
				listener.error(bean.getThreadKey(), e);
			}
		} finally {
			if (listener != null) {
				listener.stop(bean.getThreadKey(), endPosition>0 && downloadFileLength >= endPosition);
			}
			if (httpGet != null) {
				httpGet.abort();
			}
		}
	}

	private static HttpResponse doHttpRequest(HttpUriRequest httpUriRequest, DownloaderListener listener) throws ClientProtocolException,
			IOException {
		if (httpUriRequest == null) {
			return null;
		}

		BasicHttpParams httpParams = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(httpParams, DownloaderContext.CONNECTION_TIMEOUT);
		HttpConnectionParams.setSoTimeout(httpParams, DownloaderContext.DOWNLOAD_DATA_TIMEOUT);
		HttpClient httpClient = new DefaultHttpClient(httpParams);
		return httpClient.execute(httpUriRequest);
	}

	private static long downloadContent(InputStream inputStream, DownloaderBean bean, DownloaderListener listener) {
		RandomAccessFile outputStream = null;
		long currentDownloadFileLength = bean.getStartPosition();
		long endPosition = bean.getEndPosition();
		try {
			File file = FileUtils.getFile(bean.getFileSavePath());
			outputStream = new RandomAccessFile(file, "rwd");
			outputStream.seek(currentDownloadFileLength);

			if (listener != null) {
				listener.progress(bean.getThreadKey(), currentDownloadFileLength, endPosition);
			}

			int count = 0;
			byte[] buffer = new byte[DownloaderContext.TEMP_BUFF_SIZE];
			while ((count = inputStream.read(buffer, 0, buffer.length)) != -1) {
				if (Thread.currentThread().isInterrupted()) {
					break;
				}

				outputStream.write(buffer, 0, count);
				currentDownloadFileLength += count;

				if (listener != null) {
					listener.progress(bean.getThreadKey(), currentDownloadFileLength, endPosition);
				}
			}
		} catch (IOException e) {
			if (listener != null) {
				listener.error(bean.getThreadKey(), e);
			}
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		return currentDownloadFileLength;
	}

}
